using System;
using System.Collections.Generic;
using System.Xml.Linq;
using WB.Mvc.Forms.Widgets;

namespace WB.Mvc.Forms
{
	/// <summary>
	/// A form field.
	/// </summary>
	[Serializable]
	public abstract class Field
	{
		private string errorMessage;
		private XElement label;
		private bool required = true;
		private bool isValid = true;
		private List<string> classes = new List<string>();

		/// <summary>
		/// Creates an instance of <see cref="Field"/>.
		/// </summary>
		/// <param name="name">Name of the field.</param>
		protected Field(string name)
		{
			Name = name;
			errorMessage = string.Format("{0} is required.", name.ToHumanReadable());
			label = new XElement("label", new XAttribute("for", name), new XText(name.ToHumanReadable()));
		}

		/// <summary>
		/// Name of the field.
		/// </summary>
		public string Name
		{
			get; set;
		}

		/// <summary>
		/// Gets the widget used to render the input for this field.
		/// </summary>
		public abstract Widget GetWidget();

		/// <summary>
		/// Get or set an <see cref="XElement"/> for the field's label.
		/// </summary>
		public virtual XElement Label
		{
			get { return label; }
			set { label = value; }
		}

		public virtual XElement GetValidator()
		{
			return new XElement("span");
		}

		/// <summary>
		/// Value of the field.
		/// </summary>
		public virtual string Value
		{
			get; set;
		}

		/// <summary>
		/// Get or set whether the field is required.
		/// Defaults to true.
		/// </summary>
		public virtual bool Required
		{
			get { return required; }
			set { required = value; }
		}

		/// <summary>
		/// Error message if the field is invalid.
		/// <seealso cref="IsValid"/>
		/// </summary>
		public virtual string ErrorMessage
		{
			get { return errorMessage; }
			set { errorMessage = value; }
		}

		/// <summary>
		/// Returns an XHTML string representing the field.
		/// </summary>
		public virtual string ToHtml()
		{
			XElement[] elements = ToXml();

			string html = string.Empty;
			foreach (XElement ele in elements)
				html += ele.ToString();

			return html;
		}

		/// <summary>
		/// Returns an array of <see cref="XElement"/> representing the field.
		/// These elements are siblings, but no container is provided for them.
		/// Putting them in a container and acting upon them is left to the
		/// caller.
		/// </summary>
		public virtual XElement[] ToXml()
		{
			// TODO: Get this display logic out of the domain.
			XElement label = Label;

			if (Required)
				AddClass("required");

			XElement input = GetWidget();
			var error = new XElement("span", IsValid ? "" : ErrorMessage);
			return new[] { label, input, error };
		}

		private void AddClass(string klass)
		{
			if (!classes.Contains(klass))
				classes.Add(klass);
		}

		/// <summary>
		/// Validate the input of the field.
		/// </summary>
		public virtual void Validate()
		{
			if (Required)
			{
				if (Value == null)
					isValid = false;

				isValid = !String.IsNullOrEmpty(Value.ToString());
			}
			else
			{
				isValid = true;
			}
		}

		/// <summary>
		/// Returns <c>true</c> if the value of the field is valid; <c>false</c> if it is not.
		/// Defaults to <c>true</c>.
		/// Calls to <see cref="Validate"/> set the value of this property.
		/// </summary>
		public virtual bool IsValid
		{
			get
			{
				return isValid;
			}
		}
	}
}